package com.cardone.common.utils;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.List;

import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;

import org.apache.commons.lang3.ArrayUtils;
import org.springframework.beans.BeansException;

import com.google.common.base.Objects;
import com.google.common.collect.Lists;

/**
 * bean 工具
 * 
 * @author yaoht
 * 
 */
@Slf4j
public class BeanUtils {
	/**
	 * 复制属性
	 * 
	 * @param source
	 *            源对象
	 * @param target
	 *            目标对象
	 * @param includeProperties
	 *            包含属性
	 * @throws BeansException
	 */
	public static void copyProperties(@NonNull final Object source, @NonNull final Object target, final String... includeProperties)
	        throws BeansException {
		final Class<?> actualEditable = target.getClass();

		final PropertyDescriptor[] targetPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);

		for (final PropertyDescriptor targetPd : targetPds) {
			if ((targetPd.getWriteMethod() != null)
			        && ((includeProperties == null) || (ArrayUtils.contains(includeProperties, targetPd.getName())))) {
				final PropertyDescriptor sourcePd = org.springframework.beans.BeanUtils.getPropertyDescriptor(source.getClass(),
				        targetPd.getName());

				if ((sourcePd != null) && (sourcePd.getReadMethod() != null)) {
					final Method readMethod = sourcePd.getReadMethod();

					methodSetAccessibleTrue(readMethod);

					try {
						final Object value = readMethod.invoke(source);

						final Method writeMethod = targetPd.getWriteMethod();

						methodSetAccessibleTrue(writeMethod);

						writeMethod.invoke(target, value);
					} catch (final IllegalAccessException e) {
						BeanUtils.log.error(e.getMessage(), e);
					} catch (final IllegalArgumentException e) {
						BeanUtils.log.error(e.getMessage(), e);
					} catch (final InvocationTargetException e) {
						BeanUtils.log.error(e.getMessage(), e);
					}
				}
			}
		}
	}

	/**
	 * 比较两个对象,得到属性值不相等的属性名称集合
	 * 
	 * @param newObject
	 *            新对象
	 * @param oldObject
	 *            旧对象
	 * @param useProperties
	 *            使用属性集合
	 * 
	 * @return 修改属性集合
	 * 
	 * @throws BeansException
	 */
	public static String[] diffProperties(@NonNull final Object newObject, @NonNull final Object oldObject,
	        final String... useProperties) throws BeansException {
		final Class<?> actualEditable = oldObject.getClass();

		final PropertyDescriptor[] oldPds = org.springframework.beans.BeanUtils.getPropertyDescriptors(actualEditable);

		final List<String> usePropertieList = Lists.newArrayList();

		for (final PropertyDescriptor oldPd : oldPds) {
			if ((oldPd.getReadMethod() == null) || !ArrayUtils.contains(useProperties, oldPd.getName())) {
				continue;
			}

			final PropertyDescriptor newPd = org.springframework.beans.BeanUtils.getPropertyDescriptor(newObject.getClass(),
			        oldPd.getName());

			if ((newPd == null) || (newPd.getReadMethod() == null)) {
				continue;
			}

			final Method newReadMethod = newPd.getReadMethod();

			methodSetAccessibleTrue(newReadMethod);

			try {
				final Object newValue = newReadMethod.invoke(newObject);

				final Method oldReadMethod = oldPd.getReadMethod();

				methodSetAccessibleTrue(oldReadMethod);

				final Object oldValue = oldReadMethod.invoke(oldObject);

				if (!Objects.equal(newValue, oldValue)) {
					usePropertieList.add(oldPd.getName());
				}
			} catch (final IllegalAccessException e) {
				BeanUtils.log.error(e.getMessage(), e);
			} catch (final IllegalArgumentException e) {
				BeanUtils.log.error(e.getMessage(), e);
			} catch (final InvocationTargetException e) {
				BeanUtils.log.error(e.getMessage(), e);
			}
		}

		return usePropertieList.toArray(ArrayUtils.EMPTY_STRING_ARRAY);
	}

	private static void methodSetAccessibleTrue(final Method readMethod) {
		if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
			readMethod.setAccessible(true);
		}
	}
}